/***************************************************************
 * Name:      IrReaderClientWindowsImpl.cpp
 * Purpose:   Windows Implementation of IrReader Client
 * Author:    Jimmy F.Klarke (isjfk@163.com)
 * Created:   2009-08-15
 * Copyright: Jimmy F.Klarke (http://www.isjfk.org)
 * License:
 **************************************************************/

#include <vector>
#include <string>

#include <wx/intl.h>
#include <wx/msw/registry.h>

#include "IrReaderClientWindowsImpl.h"

using namespace std;

IrReaderClientWindowsImpl::IrReaderClientWindowsImpl()
{
}

vector<string> IrReaderClientWindowsImpl::getPortList()
{
    if (portList.empty()) {
        wxRegKey serialRegKeys(_T("HKEY_LOCAL_MACHINE\\HARDWARE\\DEVICEMAP\\SERIALCOMM"));

        serialRegKeys.Open(wxRegKey::Read);
        if (serialRegKeys.Exists()) {
            size_t valueSize;
            long valueIndex;
            wxString valueName;
            wxString valueData;

            serialRegKeys.GetKeyInfo(NULL, NULL, &valueSize, NULL);

            serialRegKeys.GetFirstValue(valueName, valueIndex);
            serialRegKeys.QueryValue(valueName, valueData);
            for (size_t i = 0; i < valueSize; i++) {
                portList.push_back(string(valueData.mb_str()));
                serialRegKeys.GetNextValue(valueName, valueIndex);
                serialRegKeys.QueryValue(valueName, valueData);
            }

            sort(portList.begin(), portList.end());
        }
    }

    return portList;
}

string IrReaderClientWindowsImpl::getPortDefault()
{
    vector<string> portList = getPortList();
    return portList.empty() ? "COM1" : portList[0];
}

vector<string> IrReaderClientWindowsImpl::getBaudList()
{
    if (baudList.empty()) {
        baudList.push_back("110");
        baudList.push_back("300");
        baudList.push_back("600");
        baudList.push_back("1200");
        baudList.push_back("2400");
        baudList.push_back("4800");
        baudList.push_back("9600");
        baudList.push_back("14400");
        baudList.push_back("19200");
        baudList.push_back("38400");
        baudList.push_back("57600");
        baudList.push_back("115200");
        baudList.push_back("128000");
        baudList.push_back("256000");
    }

    return baudList;
}

string IrReaderClientWindowsImpl::getBaudDefault()
{
    return "9600";
}

vector<string> IrReaderClientWindowsImpl::getDataBitSizeList()
{
    if (dataBitSizeList.empty()) {
        dataBitSizeList.push_back("5");
        dataBitSizeList.push_back("6");
        dataBitSizeList.push_back("7");
        dataBitSizeList.push_back("8");
    }

    return dataBitSizeList;
}

string IrReaderClientWindowsImpl::getDataBitSizeDefault()
{
    return "8";
}

vector<string> IrReaderClientWindowsImpl::getStopBitSizeList()
{
    if (stopBitSizeList.empty()) {
        stopBitSizeList.push_back("1");
        stopBitSizeList.push_back("1.5");
        stopBitSizeList.push_back("2");
    }

    return stopBitSizeList;
}

string IrReaderClientWindowsImpl::getStopBitSizeDefault()
{
    return "1";
}

vector<string> IrReaderClientWindowsImpl::getParityList()
{
    if (parityList.empty()) {
        parityList.push_back("None");
        parityList.push_back("Odd");
        parityList.push_back("Even");
        parityList.push_back("Mark");
        parityList.push_back("Space");
    }

    return parityList;
}

string IrReaderClientWindowsImpl::getParityDefault()
{
    return "None";
}

vector<string> IrReaderClientWindowsImpl::getFlowCtrlList()
{
    if (flowCtrlList.empty()) {
        flowCtrlList.push_back("None");
        flowCtrlList.push_back("Hardware");
        flowCtrlList.push_back("Software");
    }

    return flowCtrlList;
}

string IrReaderClientWindowsImpl::getFlowCtrlDefault()
{
    return "None";
}

void IrReaderClientWindowsImpl::openClient(
        string port,
        string baud,
        string dataBitSize,
        string stopBitSize,
        string parity,
        string flowCtrl)
{
    // open serial port
    wxString wxPort = wxString::FromUTF8(("\\\\.\\" + port).c_str());
    client = CreateFile(
            (LPCTSTR) wxPort.wc_str(),
            GENERIC_READ | GENERIC_WRITE,
            0,
            0,
            OPEN_EXISTING,
            0,
            0);
    if (client == INVALID_HANDLE_VALUE) {
        client = 0;
        throw IrReaderClientException(getErrorMsg(GetLastError()));
    }

    // set serial port parameters
    DCB dcb;

    FillMemory(&dcb, sizeof(dcb), 0);
    if (!GetCommState(client, &dcb)) {
        closeClient();
        throw IrReaderClientException(getErrorMsg(GetLastError()));
    }

    dcb.BaudRate = atoi(baud.c_str());
    dcb.ByteSize = atoi(dataBitSize.c_str());
    if (stopBitSize == "1") {
        dcb.StopBits = ONESTOPBIT;
    } else if (stopBitSize == "1.5") {
        dcb.StopBits = ONE5STOPBITS;
    } else if (stopBitSize == "2") {
        dcb.StopBits = TWOSTOPBITS;
    }
    if (parity == "None") {
        dcb.Parity = NOPARITY;
    } else if (parity == "Odd") {
        dcb.Parity = ODDPARITY;
    } else if (parity == "Even") {
        dcb.Parity = EVENPARITY;
    } else if (parity == "Mark") {
        dcb.Parity = MARKPARITY;
    } else if (parity == "Space") {
        dcb.Parity = SPACEPARITY;
    }

    dcb.fBinary = TRUE;
    dcb.fParity = FALSE;
    if (flowCtrl == "Hardware") {
        dcb.fOutxCtsFlow = TRUE;
        dcb.fOutxDsrFlow = TRUE;
        dcb.fDtrControl = DTR_CONTROL_ENABLE;
        dcb.fRtsControl = RTS_CONTROL_ENABLE;
        dcb.fDsrSensitivity = TRUE;
    } else {
        dcb.fOutxCtsFlow = FALSE;
        dcb.fOutxDsrFlow = FALSE;
        dcb.fDtrControl = DTR_CONTROL_DISABLE;
        dcb.fRtsControl = RTS_CONTROL_DISABLE;
        dcb.fDsrSensitivity = FALSE;
    }
    if (flowCtrl == "Software") {
        dcb.fInX = TRUE;
        dcb.fOutX = TRUE;
        dcb.fTXContinueOnXoff = FALSE;
    } else {
        dcb.fInX = FALSE;
        dcb.fOutX = FALSE;
        dcb.fTXContinueOnXoff = TRUE;
    }
    dcb.fErrorChar = FALSE;
    dcb.fNull = FALSE;
    dcb.fAbortOnError = FALSE;

    if (!SetCommState(client, &dcb)) {
        closeClient();
        throw IrReaderClientException(getErrorMsg(GetLastError()));
    }

    // set serial port timeout
    COMMTIMEOUTS timeouts;

    if (!GetCommTimeouts(client, &timeouts))
    {
        closeClient();
        throw IrReaderClientException(getErrorMsg(GetLastError()));
    }

    timeouts.ReadIntervalTimeout = 20;
    timeouts.ReadTotalTimeoutMultiplier = 0;
    timeouts.ReadTotalTimeoutConstant = 100;
    timeouts.WriteTotalTimeoutMultiplier = 0;
    timeouts.WriteTotalTimeoutConstant = 100;

    if (!SetCommTimeouts(client, &timeouts))
    {
        closeClient();
        throw IrReaderClientException(getErrorMsg(GetLastError()));
    }

    // write mcu config
    writeConfig();
}

void IrReaderClientWindowsImpl::closeClient()
{
    if (client != 0) {
        bool b = CloseHandle(client);
        client = 0;
        if (!b) {
            throw IrReaderClientException(getErrorMsg(GetLastError()));
        }
    }
}

vector<unsigned char> IrReaderClientWindowsImpl::read()
{
    vector<unsigned char> signals;
    unsigned char data[1024];
    DWORD bytesRead;

    if (!ReadFile(client, data, sizeof(data), &bytesRead, NULL)) {
        closeClient();
        throw IrReaderClientException(getErrorMsg(GetLastError()));
    }

    for (size_t i = 0; i < bytesRead; i++) {
        signals.push_back(data[i]);
    }

    return signals;
}

void IrReaderClientWindowsImpl::writeConfig()
{
    DWORD dwWritten;
    if (!WriteFile(client, mcuConfig, sizeof(mcuConfig), &dwWritten, NULL)) {
        closeClient();
        throw IrReaderClientException(getErrorMsg(GetLastError()));
    }
}

string getErrorMsg(DWORD errorNo) {
    LPSTR lpBuffer;

    FormatMessage(
            FORMAT_MESSAGE_ALLOCATE_BUFFER
                | FORMAT_MESSAGE_IGNORE_INSERTS
                | FORMAT_MESSAGE_FROM_SYSTEM,
            NULL,
            errorNo,
            0,
            (LPTSTR) &lpBuffer,
            0,
            NULL);
    string msg = lpBuffer ? string(lpBuffer) : string(wxString::Format(_T("Error Code: %d"), errno).mb_str());
    LocalFree(lpBuffer);

    return msg;
}
